package com.song.server;

import com.song.NacosServiceRegistry.NacosNacosServiceRegistryImpl;
import com.song.Utils.ShutdownHook;
import com.song.serprovider.ServiceProviderImpl;
import com.song.transport.*;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

/**
 * 服务端使用Netty进行socket通信
 * Netty 中有一个很重要的设计模式——责任链模式，责任链上有多个处理器，每个处理器都会对数据进行加工，并将处理后的数据传给下一个处理器。
 * 代码中的 CommonEncoder、CommonDecoder和NettyServerHandler 分别就是编码器，解码器和数据处理器。因为数据从外部传入时需要解码，
 * 而传出时需要编码，类似计算机网络的分层模型，每一层向下层传递数据时都要加上该层的信息，而向上层传递时则需要对本层信息进行解码
 * 解码在NettyClient中实现
 *
 *
 * Channel：Netty 网络操作抽象类，它除了包括基本的 I/O 操作，如 bind、connect、read、write 等。
 * EventLoop：主要是配合 Channel 处理 I/O 操作，用来处理连接的生命周期中所发生的事情。
 * ChannelFuture：Netty 框架中所有的 I/O 操作都为异步的，因此我们需要 ChannelFuture 的 addListener()注册一个 ChannelFutureListener 监听事件，当操作执行成功或者失败时，监听就会自动触发返回结果。
 * ChannelHandler：充当了所有处理入站和出站数据的逻辑容器。ChannelHandler 主要用来处理各种事件，这里的事件很广泛，比如可以是连接、数据接收、异常、数据转换等。
 * ChannelPipeline：为 ChannelHandler 链提供了容器，当 channel 创建时，就会被自动分配到它专属的 ChannelPipeline，这个关联是永久性的
 */
@Data
@Slf4j(topic = "c.NettyServer")
//public class NettyServer implements RpcServer {
public class NettyServer extends AbstractRpcServer {

//    private String host;
//    private int port;
    //抽象类已经声明，实现类可以不用声明
    private final CommonSerializer serializer;

    //NacosNacosServiceRegistryImpl serviceRegistry;
    public NettyServer(String host, int port, Integer serializer) {
        this.host = host;
        this.port = port;
        this.serializer = CommonSerializer.getByCode(serializer);

        serviceProvider = new ServiceProviderImpl();
        serviceRegistry = new NacosNacosServiceRegistryImpl();//开启Nacos

        try {
            scanServices();
        } catch (Exception e) {
            log.error("自动扫描初始化错误");
            e.printStackTrace();
        }
    }

//    //测试使用，应该使用自动扫描注册本地注册表
//    private ServiceProvider serviceProvider = new ServiceProviderImpl();
//    public <T> void addService(Class<T> serviceClass, String serviceName) {
//        serviceProvider.addServiceProvider(serviceClass, serviceName);
//    }

    //向Nacos注册服务
//    @Override
//    public <T> void publishService(Class<T> serviceClass) {
//        if(serializer == null) {
//            log.error("未设置序列化器");
//            throw new RpcException(RpcError.SERIALIZER_NOT_FOUND);
//        }
//        //向Nacos注册 通用接口的 服务名称（规范名称） 和 服务器的Socket地址
//        serviceRegistry.register(serviceClass.getCanonicalName(), new InetSocketAddress(host, port));
//        start();
//        // publishService 需要将服务保存在本地的注册表，同时注册到 Nacos 上。我这里的实现是注册完一个服务后直接调用 start() 方法，这是个不太好的实现……导致一个服务端只能注册一个服务，之后可以多注册几个然后再手动调用 start() 方法
//    }

    @Override
    public void start() {
        EventLoopGroup bossGroup = new NioEventLoopGroup();//EventLoop本质是**一个单线程执行器**(同时维护了一个Selector)，里面有run方法处理Channel.上源源不断的io事件。
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {

            ServerBootstrap serverBootstrap = new ServerBootstrap();//启动器，负责组装netty组件，启动服务器

                                //parentGroup 和 childGroup
            serverBootstrap.group(bossGroup, workerGroup)
                    //group组 添加了两个Selector bossGroup负责处理 TCP/IP 连接的 不断监听客户端的连接  针对不同客户端的连接请求处理  连接成功后 从workerGroup中选出一个EventLoop绑定到此客户连接  workerGroup负责处理 Channel（通道）的 I/O 事件
                    .channel(NioServerSocketChannel.class)//3.选择服务器的ServerSocketChannel
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .option(ChannelOption.SO_BACKLOG, 256)
                    .option(ChannelOption.SO_KEEPALIVE, true)//打开心跳机制
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .childHandler(//4.boss处理连接，work（child）处理读写，决定了work（child）能执行哪些操作（handler）
                            new ChannelInitializer<SocketChannel>() {//5.channel 代表和客户端进行数据读写的通道 Initializer初始化，负责添加别的handler
                        // 入站处理过程
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //pipeline 中的所有的处理器都有机会处理它，因此，对于入栈的请求，全部从头节点开始往后传播，一直传播到尾节点（来到尾节点的 msg 会被释放掉）。
                            ChannelPipeline pipeline = ch.pipeline();//责任链模式
                            pipeline.addLast(new CommonEncoder(new KryoSerializer()));//Kyro替换Json序列化器
                            pipeline.addLast(new CommonDecoder());//解码  将解码结果传给下一个handle节点
                            pipeline.addLast(new NettyServerHandler());//对收到消息的请求信息进行处理 此处应该使用Nacos注册  此处处理读事件
                        }
                    });
            ChannelFuture future = serverBootstrap.bind(host, port).sync();//7.绑定监听端口号

            //在关闭此服务端前  在Nacos进行 注销该服务端的所有服务
            ShutdownHook.getShutdownhook().addClearAllHook();

            future.channel().closeFuture().sync();//同步等待channel关闭

        } catch (InterruptedException e) {
            log.error("启动服务器时有错误发生: ", e);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
